﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Lime49.UI;
using LockCrypt.Core;
using LockCrypt.DAL;

namespace LockCrypt {
    /// <summary>
    /// Window for adding/editing accounts.
    /// </summary>
    public partial class WinAddAccount : Window {
        private readonly bool _editing = false;
        private readonly string _parentId = null;
        private readonly string _originalName = string.Empty;
        private IList<ITemplate> _templates = new List<ITemplate>();
        private readonly AutoTypeRecorder _actionRecorder;

        public static readonly DependencyProperty AccountProperty = DependencyProperty.Register("Account", typeof(IAccount), typeof(WinAddAccount), new PropertyMetadata(new Account(), new PropertyChangedCallback(FieldContainerChanged)));
        /// <summary>
        /// Gets or sets the account being edited.
        /// </summary>
        /// <value>The account.</value>
        public IAccount Account {
            get { return base.GetValue(AccountProperty) as Account; }
            set { base.SetValue(AccountProperty, value); }
        }

        /// <summary>
        /// Updates the template fields displayed when the selected template changes.
        /// </summary>
        /// <param name="d">The d.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private static void FieldContainerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            WinAddAccount dlg = d as WinAddAccount;
            IFieldContainer newContainer = e.NewValue as IFieldContainer;
            dlg.fieldEditor.Fields = newContainer.Fields.Select(f => new Field(f)).ToList();
        }

        /// <summary>
        /// Initializes a new <see cref="WinAddAccount"/>.
        /// </summary>
        public WinAddAccount() {
            InitializeComponent();
            Account = new Account();
            _actionRecorder = new AutoTypeRecorder();
            _actionRecorder.EnableRecording();
        }

        /// <summary>
        /// Initializes a new <see cref="WinAddAccount"/>.
        /// </summary>
        public WinAddAccount(string parentId) 
            :this() {
            _parentId = parentId;
        }

        /// <summary>
        /// Initializes a new <see cref="WinAddAccount"/> to edit an account.
        /// </summary>
        /// <param name="account">The account to edit.</param>
        public WinAddAccount(IAccount account) 
            :this() {
            this.Account = account;
            _editing = true;
            _originalName = account.Name;
            Title = I18nUtils.GetString("Strings", "EditAccount");
        }

        /// <summary>
        /// Loads available parent groups when the Window loads.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            txtName.Focus();
            txtName.SelectAll();
            LoadIconNames();
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => {
                dwe.Result = new KeyValuePair<IList<ITemplate>, IList<IGroup>>(DalConfigurationManager.Provider.GetTemplates(), DalConfigurationManager.Provider.GetGroups());
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, rwe.Error, I18nUtils.GetString("Errors", "Error"));
                } else if(rwe.Result is KeyValuePair<IList<ITemplate>, IList<IGroup>>) {
                    var result = (KeyValuePair<IList<ITemplate>, IList<IGroup>>)rwe.Result;
                    _templates = result.Key;
                    var groups = result.Value;
                    groups.Insert(0, Group.Empty);
                    cboGroup.ItemsSource = groups;
                    cboGroup.IsEnabled = true;

                    IGroup defaultGroup = null;
                    if(_parentId == null) { // add to root group
                        if(_editing) { // select existing group, or the default
                            defaultGroup = groups.FirstOrDefault(g => g.ID == Account.ParentID);
                        }
                        if(defaultGroup == null) {
                            // parent deleted (shouldn't happen if editing)
                            defaultGroup = groups.FirstOrDefault();
                        }
                        Account.ParentID = defaultGroup.ID;
                    } else { // adding new account, and should pre-select a group
                        Account.ParentID = _parentId;
                    }

                    ITemplate templateToUse = null;
                    if(_editing) { // find template
                        templateToUse = _templates.FirstOrDefault(t => t.ID == Account.Template.ID);
                        if(templateToUse == null) { // template has been deleted
                            templateToUse = Account.DeriveTemplate();
                            _templates.Add(templateToUse);
                        }
                    } else { // use the default
                        templateToUse = _templates.FirstOrDefault(t => t.Name == I18nUtils.GetString("Strings", "Blank")) ?? _templates.FirstOrDefault();
                    }
                    cboTemplate.ItemsSource = _templates;
                    cboTemplate.IsEnabled = true;
                    if(templateToUse != null) {
                        Account.Template = templateToUse;
                    }
                }
            };
            cboGroup.IsEnabled = false;
            cboTemplate.IsEnabled = false;
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Loads available icon names.
        /// </summary>
        private void LoadIconNames() {
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => {
                dwe.Result = LockCryptBLL.GetIconNames(32);
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, rwe.Error, I18nUtils.GetString("Errors", "Error"));
                } else if(rwe.Result is Dictionary<string, string>) {
                    lstIcon.ItemsSource = rwe.Result as Dictionary<string, string>;
                }
            };
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Uninstalls keyboared and mouse hooks when then Window closes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        private void Window_Closing(object sender, CancelEventArgs e) {
            _actionRecorder.DisableRecording();
        }

        /// <summary>
        /// Checks the data entered is valid.
        /// </summary>
        /// <returns><c>True</c> if the data is valid, otherwise <c>false</c></returns>
        private bool Validate() {
            bool valid = true;
            if(txtName.Text.Trim().Length == 0) {
                DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "AccountNameRequired"), I18nUtils.GetString("Errors", "Error"));
                tbcTabs.SelectedItem = tabGeneral;
                txtName.Focus();
                valid = false;
            } else if(!string.IsNullOrEmpty(txtAutoTypeConfig.Text) && string.IsNullOrEmpty(Account.AutoTypeConfig.TargetWindow)) { // config specified without a target window.
                DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "TargetWindowRequired"), I18nUtils.GetString("Errors", "Error"));
                tbcTabs.SelectedItem = tabAutoType;
                valid = false;
            } else if(!fieldEditor.ValidateFields()) {
                tbcTabs.SelectedItem = tabGeneral;
                valid = false;
            }
            return valid;
        }

        /// <summary>
        /// Updates the fields when the selection changes from the field tab.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void tbcTabs_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if(e.RemovedItems.Contains(tabGeneral) && tbcTabs.SelectedItem != tabGeneral) {
                if(fieldEditor.ValidateFields()) {
                    SaveFields();
                } else {
                    tbcTabs.SelectedItem = tabGeneral;
                }
            }
        }

        /// <summary>
        /// Saves the account.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Save(object sender, ExecutedRoutedEventArgs e) {
            if(Validate()) {
                SaveFields();
                Account.AutoTypeConfig.ParseCommandString(txtAutoTypeConfig.Text);
                DialogResult = true;
            }
        }

        /// <summary>
        /// Saves the fields from the field editor.
        /// </summary>
        private void SaveFields() {
            Account.Fields.Clear();
            IEnumerable<Field> newFields = fieldEditor.GetFields();
            foreach(Field field in newFields) {
                Account.Fields.Add(field);
            }
            btnInsertField.GetBindingExpression(MenuButton.MenuProperty).UpdateTarget();
        }

        /// <summary>
        /// Shows the help file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowHelp(object sender, ExecutedRoutedEventArgs e) {
            WPFUtils.OpenHelp("Accounts.html");
        }

        /// <summary>
        /// Closes the window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            DialogResult = false;
        }

        /// <summary>
        /// Opens the icons directory.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void OpenIconsDirectory(object sender, ExecutedRoutedEventArgs e) {
            string iconPath = System.IO.Path.Combine(Utils.GetApplicationPath(), "Icons");
            try {
                System.Diagnostics.Process.Start(iconPath);
            } catch(Exception ex) {
                DialogBox.ShowAlert(this, ex.Message, I18nUtils.GetString("Errors", "Error"));
            }
        }

        /// <summary>
        /// Updates the fields shown when the template changes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void cboTemplate_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            ITemplate template = cboTemplate.SelectedItem as ITemplate;
            if(template != null && template.ID != Account.Template.ID) {
                Account.Template = template;
                fieldEditor.Fields = template.Fields.Select(f => new Field(f)).ToList();
            }
        }

        #region AutoType
        /// <summary>
        /// Determines whether the 'Record' command can be executed by the specified sender.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteRecord(object sender, CanExecuteRoutedEventArgs e) {
            bool startRecording = Convert.ToBoolean(e.Parameter);
            if(!IsLoaded) {
                e.CanExecute = false;
            } else if(startRecording && !_actionRecorder.IsRecording) {
                // request start and no recording in progress
                e.CanExecute = true;
            } else if(!startRecording && _actionRecorder.IsRecording) {
                // request stop and recording in progress
                e.CanExecute = true;
            } else {
                e.CanExecute = false;
            }
        }

        /// <summary>
        /// Start or stops recording user input.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Record(object sender, ExecutedRoutedEventArgs e) {
            if(!_actionRecorder.IsRecording) {
                _actionRecorder.RecordAction();
            } else {
                _actionRecorder.StopRecording();
                int currentPos = txtAutoTypeConfig.CaretIndex;
                string recordedAction = _actionRecorder.RecordedAction;
                if(recordedAction.Length > 0) {
                    txtAutoTypeConfig.Text = txtAutoTypeConfig.Text.Insert(currentPos <= 0 ? txtAutoTypeConfig.Text.Length : currentPos, recordedAction);
                }
            }
        }

        private void CanInsertAutoTypeMeta(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = IsLoaded && !_actionRecorder.IsRecording;
        }

        /// <summary>
        /// Shows a prompt to insert a delay at the current caret position.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void InsertDelay(object sender, ExecutedRoutedEventArgs e) {
            int delay = Convert.ToInt32(e.Parameter);
            if(delay == 0) {
                WinInputDialog dlg = new WinInputDialog(I18nUtils.GetString("Strings", "InsertDelay"), I18nUtils.GetString("Strings", "DelayInMillis")) { Owner = this };
                dlg.ValidateInput += () => {
                    if(!int.TryParse(dlg.UserInput, out delay) || delay <= 0) {
                        DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "NumberRequiredFmt", I18nUtils.GetString("Strings", "Delay")), I18nUtils.GetString("Errors", "Error"));
                        return false;
                    } else {
                        return true;
                    }
                };
                if(dlg.ShowDialog() != true)
                    return;
            }
            int currentPos = txtAutoTypeConfig.CaretIndex;
            txtAutoTypeConfig.Text = txtAutoTypeConfig.Text.Insert(currentPos <= 0 ? txtAutoTypeConfig.Text.Length : currentPos, string.Format("{{Wait:{0}}}", delay * 1000)); // milliseconds
        }

        /// <summary>
        /// Inserts a field at the current caret position.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void InsertField(object sender, ExecutedRoutedEventArgs e) {
            string fieldName = Convert.ToString(e.Parameter);
            if(string.IsNullOrEmpty(fieldName))
                return;
            int currentPos = txtAutoTypeConfig.CaretIndex;
            txtAutoTypeConfig.Text = txtAutoTypeConfig.Text.Insert(currentPos <= 0 ? txtAutoTypeConfig.Text.Length : currentPos, string.Format("${0}$", fieldName));
            /*string currentConfig = txtAutoTypeConfig.Text.Trim();
            bool needKeyWrapper = true;
            if(txtAutoTypeConfig.CaretIndex > 0) {
                // fields must occur within a {Keys} section
                string firstHalf = currentConfig.Substring(0, txtAutoTypeConfig.CaretIndex),
                       secondHalf = currentConfig.Substring(txtAutoTypeConfig.CaretIndex);
                int lastKeysStartPosition = currentConfig.LastIndexOf("{Keys");
                if(lastKeysStartPosition >= 0) {
                    int keyEndPosition = currentConfig.IndexOf("}", lastKeysStartPosition); // the closing brace for the last keys 
                    var x = System.Text.RegularExpressions.Regex.Match(
                }*/
        }
        #endregion
    }
}
